﻿using System;
using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using OpenSlideNET.Interop;
using WholeSlideImageLibrary.Features;

namespace WholeSlideImageLibrary.OpenSlide
{
    internal sealed class OpenSlideWholeSlideImage : WholeSlideImage, ITextPropertiesFeature, IAssociatedImagesFeature
    {
#pragma warning disable CA2213 // 应释放可释放的字段
        private OpenSlideImageSafeHandle? _handle;
#pragma warning restore CA2213 // 应释放可释放的字段

        public OpenSlideWholeSlideImage(string fileName)
        {
            OpenSlideImageSafeHandle? handle = OpenSlideInterop.Open(fileName);
            if (handle.IsInvalid)
            {
                throw new InvalidDataException();
            }
            if (!TryCheckError(handle, out string? errMsg))
            {
                handle.Close();
                throw new InvalidDataException(errMsg);
            }
            _handle = handle;
        }

        private int? _levelCountCache;

        private int CacheLevelCount()
        {
            OpenSlideImageSafeHandle? handle = EnsureNotDisposed();

            int result = OpenSlideInterop.GetLevelCount(handle);
            if (result == -1)
            {
                CheckAndThrowError(handle);
            }
            _levelCountCache = result;
            return result;
        }

        public override int LevelCount => _levelCountCache is null ? CacheLevelCount() : _levelCountCache.GetValueOrDefault();

        private Size? _dimensionsCache;

        private Size CacheDimensions()
        {
            OpenSlideImageSafeHandle? handle = EnsureNotDisposed();

            OpenSlideInterop.GetLevel0Dimensions(handle, out long w, out long h);
            if (w == -1 || h == -1)
            {
                CheckAndThrowError(handle);
            }

            var size = new Size((int)w, (int)h);
            _dimensionsCache = size;
            return size;
        }

        public override int Width => _dimensionsCache is null ? CacheDimensions().Width : _dimensionsCache.GetValueOrDefault().Width;

        public override int Height => _dimensionsCache is null ? CacheDimensions().Height : _dimensionsCache.GetValueOrDefault().Height;

        public override double MicronsPerPixelX
        {
            get
            {
                if (TryGetProperty(OpenSlideInterop.Utf8OpenSlidePropertyNameMPPX, out string? value) && double.TryParse(value, out double result))
                {
                    return result;
                }
                return 0;
            }
        }

        public override double MicronsPerPixelY
        {
            get
            {
                if (TryGetProperty(OpenSlideInterop.Utf8OpenSlidePropertyNameMPPY, out string? value) && double.TryParse(value, out double result))
                {
                    return result;
                }
                return 0;
            }
        }

        public override Size GetLevelSize(int level)
        {
            OpenSlideImageSafeHandle? handle = EnsureNotDisposed();

            OpenSlideInterop.GetLevelDimensions(handle, level, out long w, out long h);
            if (w == -1 || h == -1)
            {
                CheckAndThrowError(handle);
            }

            return new Size((int)w, (int)h);
        }

        public override unsafe ValueTask ReadRegionAsync<T>(int level, int x, int y, int width, int height, Memory<T> buffer, CancellationToken cancellationToken)
        {
            OpenSlideImageSafeHandle? handle = EnsureNotDisposed();

            int levelCount = LevelCount;
            if ((uint)level > (uint)levelCount)
            {
                throw new ArgumentOutOfRangeException(nameof(level));
            }
            if (Unsafe.SizeOf<T>() * buffer.Length < 4 * width * height)
            {
                throw new ArgumentException("destination is too small.", nameof(buffer));
            }

            double downsample = OpenSlideInterop.GetLevelDownsample(handle, level);
            if (downsample == -1.0d)
            {
                CheckAndThrowError(handle);
            }

            using (MemoryHandle memoryHandle = buffer.Pin())
            {
                OpenSlideInterop.ReadRegion(handle, memoryHandle.Pointer, (long)(x * downsample), (long)(y * downsample), level, width, height);

                // BGRA to RGBA
                byte* pointer = (byte*)memoryHandle.Pointer;
                int length = width * height;
                for (int i = 0; i < length; i++)
                {
                    byte b = pointer[0];
                    byte g = pointer[1];
                    byte r = pointer[2];
                    byte a = pointer[3];

                    pointer[0] = r;
                    pointer[1] = g;
                    pointer[2] = b;
                    pointer[3] = a;

                    pointer += 4;
                }
            }

            return default;
        }

        private string[]? _properties;

        private string[] LoadProperties()
        {
            if (_properties is null)
            {
                _properties = OpenSlideInterop.GetPropertyNames(EnsureNotDisposed());
            }
            return _properties;
        }

        public ValueTask<IEnumerable<string>> GetAllTextPropertiesAsync(CancellationToken cancellationToken = default)
        {
            return new ValueTask<IEnumerable<string>>(LoadProperties());
        }

        public ValueTask<string> ReadTextPropertyAsync(string name, CancellationToken cancellationToken = default)
        {
            OpenSlideImageSafeHandle? handle = EnsureNotDisposed();
            string? value = OpenSlideInterop.GetPropertyValue(handle, name);
            return new ValueTask<string>(value ?? string.Empty);
        }

        private string[]? _associatedImageNames;

        private string[] LoadAssociatedImageNames()
        {
            if (_associatedImageNames is null)
            {
                _associatedImageNames = OpenSlideInterop.GetAssociatedImageNames(EnsureNotDisposed());
            }
            return _associatedImageNames;
        }

        public ValueTask<IEnumerable<string>> GetAllAssociatedImagesAsync(CancellationToken cancellationToken = default)
        {
            return new ValueTask<IEnumerable<string>>(LoadAssociatedImageNames());
        }

        public ValueTask<Size> GetAssociatedImageSizeAsync(string name, CancellationToken cancellationToken = default)
        {
            OpenSlideImageSafeHandle? handle = EnsureNotDisposed();

            OpenSlideInterop.GetAssociatedImageDimensions(handle, name, out long w, out long h);
            if (w == -1 || h == -1)
            {
                throw new KeyNotFoundException();
            }

            return new ValueTask<Size>(new Size((int)w, (int)h));
        }

        public unsafe ValueTask ReadAssociatedImageAsync<T>(string name, Memory<T> buffer, CancellationToken cancellationToken = default) where T : unmanaged
        {
            OpenSlideImageSafeHandle? handle = EnsureNotDisposed();

            OpenSlideInterop.GetAssociatedImageDimensions(handle, name, out long w, out long h);
            if (Unsafe.SizeOf<T>() * buffer.Length < 4 * w * h)
            {
                throw new ArgumentException("destination is too small.", nameof(buffer));
            }

            using (MemoryHandle memoryHandle = buffer.Pin())
            {
                OpenSlideInterop.ReadAssociatedImage(handle, name, memoryHandle.Pointer);

                // BGRA to RGBA
                byte* pointer = (byte*)memoryHandle.Pointer;
                int length = (int)w * (int)h;
                for (int i = 0; i < length; i++)
                {
                    byte b = pointer[0];
                    byte g = pointer[1];
                    byte r = pointer[2];
                    byte a = pointer[3];

                    pointer[0] = r;
                    pointer[1] = g;
                    pointer[2] = b;
                    pointer[3] = a;

                    pointer += 4;
                }
            }

            return default;
        }

        #region Helper

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private OpenSlideImageSafeHandle EnsureNotDisposed()
        {
            OpenSlideImageSafeHandle? handle = _handle;
            if (handle is null)
            {
                ThrowObjectDisposedException();
            }
            return handle;
        }

        [DoesNotReturn]
        private static void ThrowObjectDisposedException()
        {
            throw new ObjectDisposedException(nameof(OpenSlideWholeSlideImage));
        }

        public override ValueTask DisposeAsync()
        {
            Interlocked.Exchange(ref _handle, null)?.Dispose();
            return base.DisposeAsync();
        }

        internal static bool TryCheckError(OpenSlideImageSafeHandle handle, [NotNullWhen(false)] out string? message)
        {
            message = OpenSlideInterop.GetError(handle);
            return message is null;
        }

        internal static void CheckAndThrowError(OpenSlideImageSafeHandle handle)
        {
            string? message = OpenSlideInterop.GetError(handle);
            if (!(message is null))
            {
                throw new InvalidDataException(message);
            }
        }

        internal bool TryGetProperty(ReadOnlySpan<byte> utf8name, [NotNullWhen(true)] out string? value)
        {
            OpenSlideImageSafeHandle? handle = EnsureNotDisposed();

            value = OpenSlideInterop.GetPropertyValue(handle, utf8name);
            return !(value is null);
        }

        #endregion
    }
}
